﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Upreader.Common.Cryptography
{
    /// <summary>
    /// HashAlgorithm implementation for yEnc
    /// </summary>
    public class FromyEncTransform : HashAlgorithm
    {
        #region Configuration

        // basic decryption product
        private const byte Decryptor = 42;
        // when detected, indicatingthe next byte is escaped
        private const byte EscapedIndicator = 61;
        // decryption product for unescape
        private const byte UnescapeDecryptor = 64;

        private const byte LineFeed = 10;
        private const byte CarriageReturn = 13;

        #endregion // Configuration

        #region Fields

        // when set, the current byte will be an escaped byte and therefore should be unescaped
        private bool _isEscapedByte;
        // holds the so far built hash
        private MemoryStream _currentHashMemoryStream;
        // True if HashCore has been called
        private bool _hashCoreCalled;
        // True if HashFinal has been called
        private bool _hashFinalCalled;

        #endregion // Fields

        #region Constructor

        public FromyEncTransform()
        {
            InitializeVariables();
        }

        #endregion // Constructor

        #region Properties

        public override byte[] Hash
        {
            get
            {
                return this._currentHashMemoryStream.ToArray();
            }
        }

        #endregion // Properties

        #region Methods

        public override void Initialize()
        {
            InitializeVariables();
        }

        #endregion // Methods

        #region HashAlgorithm Implementation

        /// <summary>
        /// Updates the hash code for the provided data.
        /// </summary>
        protected override void HashCore(byte[] data, int start, int count)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            if (_hashFinalCalled)
                throw new CryptographicException("Hash not valid for use in specified state.");

            _hashCoreCalled = true;

            for (int index = start; index < count; index++)
            {
                byte currentByte = data[index];

                // when the current byte is an escaped byte, it cant have any special meaning
                if (!_isEscapedByte)
                {
                    if (currentByte == EscapedIndicator)
                    {
                        // set isEscapedByte for the next byte on true and continue with the next byte
                        _isEscapedByte = true;
                        continue;
                    }
                }

                // if we come here, the current byte is not an escapedIndicatorByte
                // for each byte perform the decode logic
                // this does not coutn for a raw lineFeed or carriage return which are part of the NNTP protocol
                if (currentByte != LineFeed && currentByte != CarriageReturn)
                {
                    if (_isEscapedByte)
                    {
                        // this is an escaped byte so unescape first
                        unchecked { currentByte -= UnescapeDecryptor; }

                        _isEscapedByte = false;
                    }

                    unchecked { currentByte -= Decryptor; }
                }

                // And write the byte to the current memory stream for output
                _currentHashMemoryStream.WriteByte(currentByte);
            }
        }

        /// <summary>
        /// Finalizes the hash code and returns it.
        /// </summary>
        /// <returns></returns>
        protected override byte[] HashFinal()
        {

            if (!_hashCoreCalled)
                throw new InvalidOperationException("Can't procceed when HashCore has not been called");

            _hashFinalCalled = true;
            return Hash;
        }

        #endregion // HashAlgorithm Implementation

        #region Helpers

        private void InitializeVariables()
        {
            _isEscapedByte = false;
            _hashCoreCalled = false;
            _hashFinalCalled = false;

            if (_currentHashMemoryStream == null)
                _currentHashMemoryStream = new MemoryStream();
        }

        #endregion // Helpers

        #region Dispose

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (_currentHashMemoryStream != null)
                _currentHashMemoryStream.Dispose();
        }

        #endregion // Dispose
    }
}
